using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

using SlimDX;
using SlimDX.Direct3D9;
using SlimDX.Windows;

using miciv.Rendering;

namespace miciv
{
    public class Text : Frame, IDiffuse, IAlbedoTexture, IParameterizable
    {
        protected class LineLayoutInfo
        {
            protected int m_sWidth;
            protected int m_sY;
            protected int m_sCharCount;
            protected int m_sWordCount;
            protected int m_sCurrentWordLength;
            protected int m_sCurrentWordWidth;

            public int Width
            {
                get
                {
                    return this.m_sWidth;
                }
            }

            public int PotentialWidth
            {
                get
                {
                    return this.m_sWidth + this.m_sCurrentWordWidth;
                }
            }

            public int Y
            {
                get
                {
                    return this.m_sY;
                }
            }

            public int CharCount
            {
                get
                {
                    return this.m_sCharCount;
                }
            }

            public int WordCount
            {
                get
                {
                    return this.m_sWordCount;
                }
            }

            public int CurrentWordLength
            {
                get
                {
                    return this.m_sCurrentWordLength;
                }
            }

            public int CurrentWordWidth
            {
                get
                {
                    return this.m_sCurrentWordWidth;
                }
            }

            public LineLayoutInfo(int _sY)
            {
                this.m_sWidth = 0;
                this.m_sY = _sY;
                this.m_sCharCount = 0;
                this.m_sWordCount = 0;
            }

            public void BeginWord()
            {
                this.m_sCurrentWordLength = 0;
                this.m_sCurrentWordWidth = 0;
            }

            public void BeginWord(LineLayoutInfo _llInfo)
            {
                this.m_sCurrentWordLength = _llInfo.CurrentWordLength;
                this.m_sCurrentWordWidth = _llInfo.CurrentWordWidth;
            }

            public void AddChar(int _sWidth)
            {
                ++this.m_sCurrentWordLength;
                this.m_sCurrentWordWidth += _sWidth;
            }

            public void EndWord()
            {
                this.m_sCharCount += this.m_sCurrentWordLength;
                this.m_sWidth += this.m_sCurrentWordWidth;
                ++this.m_sWordCount;
            }
        };

        protected struct Vertex
        {
            public Vector3 Position;
            public Color4 Color;
            public Vector2 UV;
        }

        public enum EHorizJustification
        {
            Left,
            Center,
            Right
        };

        public enum EVertJustification
        {
            Top,
            Center,
            Bottom
        };

        public enum EWrap
        {
            None,
            Char,
            Word
        }

        protected Vertex[] m_vertices;
        protected string m_strStringID;
        protected Color4 m_f4Diffuse;
        protected TextureManager.ManagedTexture m_textureAlbedo;
        protected StringInfo m_stringInfo;
        protected BitmapFont m_bmFont;
        protected EHorizJustification m_hJustification;
        protected EVertJustification m_vJustification;
        protected EWrap m_wrapMode;
        protected string m_strFinalText;
        protected int m_sVertexCount;

        public Text(CoreContext _coreContext, string _strID)
            : base(_coreContext, _strID)
        {
        }

        public Text(CoreContext _coreContext, string _strID, Frame _frmParent)
            : base(_coreContext, _strID, _frmParent)
        {
        }

        public string StringID
        {
            get
            {
                return this.m_strStringID;
            }
            set
            {
                if (value != this.m_strStringID)
                {
                    this.m_strStringID = value;
                    this.m_bDirty = true;
                }
            }
        }

        public EHorizJustification HJustification
        {
            get
            {
                return this.m_hJustification;
            }
            set
            {
                this.m_hJustification = value;
                this.m_bDirty = true;
            }
        }

        public EVertJustification VJustification
        {
            get
            {
                return this.m_vJustification;
            }
            set
            {
                this.m_vJustification = value;
                this.m_bDirty = true;
            }
        }

        public EWrap WordWrap
        {
            get
            {
                return this.m_wrapMode;
            }
            set
            {
                this.m_wrapMode = value;
            }
        }

        public string Value
        {
            get
            {
                return this.m_strFinalText;
            }
        }

        override protected void InternalInitialize(CoreContext _coreContext, string _strID)
        {
            base.InternalInitialize(_coreContext, _strID);
            this.HJustification = EHorizJustification.Left;
            this.VJustification = EVertJustification.Top;
            this.Diffuse = new Color4(1.0f, 1.0f, 1.0f, 1.0f);
            this.m_wrapMode = EWrap.None;
            this.m_renderer = this.m_coreContext.GetData<RendererManager>("RendererManager").GetByVertexDefinition("TextureDiffuseVertexDefinition", this);
            this.m_sVertexCount = 0;
        }

        override public Frame Clone(Frame _frmParent, string _strID)
        {
            Frame frame = new Text(this.m_coreContext, this.m_strID + _strID, _frmParent);
            frame.Copy(this);
            foreach (Frame child in this.m_lstChildren)
            {
                child.Clone(frame);
            }
            return frame;
        }

        override public Frame Clone(Frame _frmParent)
        {
            return this.Clone(_frmParent, "");
        }

        override public void Copy(Frame _frame)
        {
            base.Copy(_frame);
            Text text = _frame as Text;
            this.HJustification = text.HJustification;
            this.VJustification = text.VJustification;
            this.Diffuse = text.Diffuse;
            this.m_wrapMode = text.WordWrap;
            this.StringID = text.StringID;
        }

        override public void UpdateData(float _deltaTime)
        {
            base.UpdateData(_deltaTime);
            if (false != this.NeedUpdate)
            {
                this.m_vertices = null;
                this.m_stringInfo = this.m_coreContext.GetData<StringTableManager>("StringTableManager").GetString(this.m_strStringID);
                this.m_bmFont = (null != this.m_stringInfo) ? this.m_coreContext.GetData<BitmapFontManager>("BitmapFontManager").GetFont(this.m_stringInfo.FontID) : null;
                this.m_textureAlbedo = this.m_bmFont.GetPage(0);
                if ((null != this.m_stringInfo) && (null != this.m_bmFont) && (null != this.m_textureAlbedo))
                {
                    BuildIndependentVertexBuffer();
                    BuildVertexFeeder();
                }
                this.m_bDirty = false;
            }
        }

        #region IColor Membres

        public Color4 Diffuse
        {
            get
            {
                return this.m_f4Diffuse;
            }
            set
            {
                if (value != this.m_f4Diffuse)
                {
                    this.m_f4Diffuse = value;
                    //this.m_bDirty = true;
                }
            }
        }

        #endregion

        #region IAlbedoTexture Membres

        public TextureManager.ManagedTexture Albedo
        {
            get
            {
                return this.m_textureAlbedo;
            }
            set
            {
                this.m_textureAlbedo = value;
            }
        }

        #endregion

        protected List<LineLayoutInfo> GetTextLineInfo()
        {
            List<LineLayoutInfo> lstLineInfo = new List<LineLayoutInfo>();

            LineLayoutInfo llInfo = new LineLayoutInfo(0);
            lstLineInfo.Add(llInfo);
            llInfo = lstLineInfo[lstLineInfo.Count - 1];
            llInfo.BeginWord();

            char cc = (char)0;
            bool bEscape = false;

            foreach (char c in this.m_strFinalText)
            {
                BitmapFontChar bmfChar = this.m_bmFont.GetChar((uint)c);
                if (null != bmfChar)
                {
                    if (('\n' == c) || ((false != bEscape) && ('n' == c)))
                    {
                        llInfo.EndWord();
                        LineLayoutInfo newllInfo = new LineLayoutInfo(llInfo.Y + (int)this.m_bmFont.Info.Size);
                        lstLineInfo.Add(newllInfo);
                        llInfo = lstLineInfo[lstLineInfo.Count - 1];
                        bEscape = false;
                    }
                    else if ('\\' == c)
                    {
                        bEscape = true;
                    }
                    else
                    {
                        int sKerning = this.m_bmFont.GetKerning(cc, c);

                        if (false != char.IsWhiteSpace(c))
                        {
                            llInfo.EndWord();
                            llInfo.BeginWord();
                        }
                        else if ((EWrap.Char == this.m_wrapMode)
                            && (m_f2Size.X < (llInfo.PotentialWidth + bmfChar.XAdvance)))
                        {
                            llInfo.EndWord();
                            LineLayoutInfo newllInfo = new LineLayoutInfo(llInfo.Y + (int)this.m_bmFont.Info.Size);
                            lstLineInfo.Add(newllInfo);
                            llInfo = lstLineInfo[lstLineInfo.Count - 1];
                            sKerning = 0;
                        }
                        else if ((EWrap.Word == this.m_wrapMode)
                            && (m_f2Size.X < (llInfo.PotentialWidth + bmfChar.XAdvance))
                            && (0 < llInfo.WordCount))
                        {
                            LineLayoutInfo newllInfo = new LineLayoutInfo(llInfo.Y + (int)this.m_bmFont.Info.Size);
                            newllInfo.BeginWord(llInfo);
                            lstLineInfo.Add(newllInfo);
                            llInfo = lstLineInfo[lstLineInfo.Count - 1];
                            sKerning = 0;
                        }
                        llInfo.AddChar(bmfChar.XAdvance + sKerning);
                    }
                }
            }
            llInfo.EndWord();

            return lstLineInfo;
        }

        protected void GetTextLineXLayout(LineLayoutInfo llInfo, out float x)
        {
            switch (this.m_hJustification)
            {
                case EHorizJustification.Center:
                {
                    x = -llInfo.Width / 2.0f;
                    break;
                }
                case EHorizJustification.Right:
                {
                    x = this.m_f2Size.X / 2.0f - llInfo.Width;
                    break;
                }
                case EHorizJustification.Left:
                default:
                {
                    x = -this.m_f2Size.X / 2.0f;
                    break;
                }
            }
        }

        protected void GetTextLineYLayout(LineLayoutInfo llInfo, out float y)
        {
            int sHeight = llInfo.Y + (int)this.m_bmFont.Info.Size;

            switch (this.m_vJustification)
            {
                case EVertJustification.Center:
                {
                    y = sHeight / 2.0f;
                    break;
                }
                case EVertJustification.Bottom:
                {
                    y = -this.m_f2Size.Y / 2.0f + sHeight;
                    break;
                }
                case EVertJustification.Top:
                default:
                {
                    y = this.m_f2Size.Y / 2.0f;
                    break;
                }
            }
        }

        protected void BuildIndependentVertexBuffer()
        {
            this.m_strFinalText = this.m_stringInfo.Text;

            // if text is associated with arguments then proceed to build final text accordingly
            if (false != this.NeedUpdateFromDataSource)
            {
                foreach (DictionaryEntry de in this.m_htblParameters)
                {
                    this.m_strFinalText = this.m_strFinalText.Replace(de.Value as string,
                        this.m_dataSource.GetData<string>(this.m_sDataSourceIndex, de.Key as string));
                }
            }

            if (false == string.IsNullOrEmpty(this.m_strFinalText))
            {
                List<LineLayoutInfo> lstLineInfo = GetTextLineInfo();
                int sStrlen = 0;
                foreach (LineLayoutInfo llInfo in lstLineInfo)
                {
                    sStrlen += llInfo.CharCount;
                }

                if (0 < sStrlen)
                {
                    int lliIndex = 0;
                    LineLayoutInfo llInfo = lstLineInfo[lliIndex];
                    int sCharVertexCount = sStrlen * 4;
                    int sLinkVertexCount = (sStrlen - 1) * 2;
                    int sCount = sCharVertexCount + sLinkVertexCount;
                    int sVertexIndex = 0;
                    int sCharIndex = 0;
                    float fTexWidth = this.m_textureAlbedo.Info.Width;
                    float fTexHeight = this.m_textureAlbedo.Info.Height;

                    float x = 0.0f;
                    float y = 0.0f;
                    float xx = 0.0f;
                    float yy = 0.0f;
                    float u = 0.0f;
                    float v = 0.0f;
                    char cc = (char)0;
                    bool bEscape = false;
                    bool bProcessChar = true;

                    this.m_sVertexCount = sCount;
                    this.m_vertices = new Vertex[sCount];

                    GetTextLineXLayout(llInfo, out x);
                    GetTextLineYLayout(lstLineInfo[lstLineInfo.Count - 1], out y);

                    foreach (char c in this.m_strFinalText)
                    {
                        BitmapFontChar bmfChar = this.m_bmFont.GetChar((uint)c);
                        if (null != bmfChar)
                        {
                            bProcessChar = true;
                            if (('\n' == c) || ((false != bEscape) && ('n' == c)) || (sCharIndex >= llInfo.CharCount))
                            {
                                bProcessChar = (sCharIndex >= llInfo.CharCount);
                                llInfo = lstLineInfo[++lliIndex];
                                GetTextLineXLayout(llInfo, out x);
                                y -= (float)this.m_bmFont.Info.Size;
                                bEscape = false;
                                sCharIndex = 0;
                            }
                            else if ('\\' == c)
                            {
                                bEscape = true;
                                bProcessChar = false;
                            }
                            if (false != bProcessChar)
                            {
                                int sKerning = this.m_bmFont.GetKerning(cc, c);

                                if ((EWrap.Char == this.m_wrapMode) && (m_f2Size.X < (x + bmfChar.XAdvance)))
                                {
                                    llInfo = lstLineInfo[++lliIndex];
                                    GetTextLineXLayout(llInfo, out x);
                                    y -= (float)this.m_bmFont.Info.Size;
                                    sKerning = 0;
                                    sCharIndex = 0;
                                }

                                if (0 != sKerning)
                                {
                                    x += sKerning;
                                }
                                xx = x + bmfChar.XOffset;
                                yy = y - bmfChar.YOffset;

                                // second vertex for link to next char
                                if (0 < sVertexIndex)
                                {
                                    u = (float)bmfChar.X / fTexWidth;
                                    v = (float)bmfChar.Y / fTexHeight;
                                    this.m_vertices[sVertexIndex - 1].Color = this.m_f4Diffuse;
                                    this.m_vertices[sVertexIndex - 1].Position = new Vector3(xx, yy, 0.0f);
                                    this.m_vertices[sVertexIndex - 1].UV = new Vector2(u, v);
                                }

                                // actual char strip
                                u = (float)bmfChar.X / fTexWidth;
                                v = (float)bmfChar.Y / fTexHeight;
                                this.m_vertices[sVertexIndex + 0].Color = this.m_f4Diffuse;
                                this.m_vertices[sVertexIndex + 0].Position = new Vector3(xx, yy, 0.0f);
                                this.m_vertices[sVertexIndex + 0].UV = new Vector2(u, v);

                                u = (float)bmfChar.X / fTexWidth;
                                v = (float)(bmfChar.Y + bmfChar.Height) / fTexHeight;
                                this.m_vertices[sVertexIndex + 1].Color = this.m_f4Diffuse;
                                this.m_vertices[sVertexIndex + 1].Position = new Vector3(xx, yy - (float)bmfChar.Height, 0.0f);
                                this.m_vertices[sVertexIndex + 1].UV = new Vector2(u, v);

                                u = (float)(bmfChar.X + bmfChar.Width) / fTexWidth;
                                v = (float)bmfChar.Y / fTexHeight;
                                this.m_vertices[sVertexIndex + 2].Color = this.m_f4Diffuse;
                                this.m_vertices[sVertexIndex + 2].Position = new Vector3(xx + (float)bmfChar.Width, yy, 0.0f);
                                this.m_vertices[sVertexIndex + 2].UV = new Vector2(u, v);

                                u = (float)(bmfChar.X + bmfChar.Width) / fTexWidth;
                                v = (float)(bmfChar.Y + bmfChar.Height) / fTexHeight;
                                this.m_vertices[sVertexIndex + 3].Color = this.m_f4Diffuse;
                                this.m_vertices[sVertexIndex + 3].Position = new Vector3(xx + (float)bmfChar.Width, yy - (float)bmfChar.Height, 0.0f);
                                this.m_vertices[sVertexIndex + 3].UV = new Vector2(u, v);

                                // first vertex for link to next char
                                if (sVertexIndex < (sCount - 4))
                                {
                                    this.m_vertices[sVertexIndex + 4].Color = this.m_f4Diffuse;
                                    this.m_vertices[sVertexIndex + 4].Position = new Vector3(xx + (float)bmfChar.Width, yy - (float)bmfChar.Height, 0.0f);
                                    this.m_vertices[sVertexIndex + 4].UV = new Vector2(u, v);
                                    x += bmfChar.XAdvance;
                                    sVertexIndex += 6;
                                }
                            }
                            ++sCharIndex;
                        }
                        // store current char for next kerning
                        cc = c;
                    }
                }
            }
        }

        protected void BuildVertexFeeder()
        {
            IVertexFeeder feeder = null;

            if (1 == this.m_vertexFeeders.Count)
            {
                if (this.m_sVertexCount != this.m_vertexFeeders[0].VertexCount)
                {
                    this.Renderer.ReleaseFeeders(this.m_vertexFeeders);
                }
                else
                {
                    feeder = this.m_vertexFeeders[0];
                }
            }

            if (false == string.IsNullOrEmpty(this.m_strFinalText))
            {
                if (null == feeder)
                {
                    feeder = this.Renderer.CreateFeeder(this.m_sVertexCount);
                    this.m_vertexFeeders.Add(feeder);
                }

                if (false != feeder.VertexLock())
                {
                    Hashtable htblData = new Hashtable(3); // 3 fields : position, color and uv
                    htblData.Add("POSITION", null);
                    htblData.Add("COLOR", null);
                    htblData.Add("UV", null);
                    uint uIndex = 0;
                    foreach (Vertex vertex in this.m_vertices)
                    {
                        htblData["POSITION"] = vertex.Position;
                        htblData["COLOR"] = vertex.Color;
                        htblData["UV"] = vertex.UV;
                        feeder.SetVertex(uIndex, htblData);
                        ++uIndex;
                    }
                    feeder.VertexUnlock();
                    feeder.PrimitiveType = PrimitiveType.TriangleStrip;
                    feeder.PrimitiveSize = this.m_vertices.Length - 2;
                }
            }
        }
    }
}
